import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.net.Socket;
import java.net.UnknownHostException;

/**
 * A class which connects to a RobotWorld server and controls a robot in the game.
 * @author ruben.ortlam
 */
public class Robot {
	public static final int PORT = 7777;
	
	private Socket socket;
	private DataOutputStream out;
	private ClientInputListener in;
	private Thread inThread;
	
	private long lastPing;
	
	private Pinger pinger;
	private Thread pingerThread;
	
	private int distance;
	private double angle;
	private int groundSensorData;
	private boolean done;
	private boolean stuck;

	public static final int GREEN = 0;
	public static final int RED = 1;
	public static final int YELLOW = 2;
	public static final int BLUE = 3;
	public static final int PURPLE = 4;
	public static final int CYAN = 5;
	public static final int ORANGE = 6;
	public static final int PINK = 7;
	
    public static final int AIR = 0;
    public static final int SOLID = 1;
    public static final int WATER = 2;
	
	/**
	 * Creates a new RobotWorld client and connects to the given ip.
	 * Throws an error and closes the program if the connection could not be established.
	 * 
	 * @param ip The IP of the RobotWorld server.
	 */
	public Robot(String ip, String name, int color) {
		try {
			socket = new Socket(ip, PORT);
			in = new ClientInputListener(new DataInputStream(socket.getInputStream()));
			out = new DataOutputStream(socket.getOutputStream());
			out.flush();
		} catch (UnknownHostException e) {
			e.printStackTrace();
			System.out.println("Unreachable Server");
			System.exit(1);
		} catch (IOException e) {
			e.printStackTrace();
		}
		
		sendClientInfo(name, color);
		
		lastPing = System.currentTimeMillis();
		
		distance = 0;
		angle = 0d;
		groundSensorData = 0;
		
		System.out.println("Connected to Server at " + socket.getRemoteSocketAddress());
		
		inThread = new Thread(in);
		inThread.start();
		
		pinger = new Pinger();
		pingerThread = new Thread(pinger);
		
		pingerThread.start();
	}
	
	private void sendClientInfo(String name, int color) {
		try {
			out.writeUTF(name + "^" + color);
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
	
	/**
	 * Sends a command to turn the controlled robot by the given angle.
	 * 
	 * @param angle The angle for the robot to turn. Should be between -PI and +PI.
	 */
	public void turn(double angle) {
		done = false;
		int tmp = (int)Math.toDegrees(angle);
		tmp <<= 4;
		tmp += 1;
		tmp <<= 4;
		
		try {
			out.writeInt(tmp);
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
	
	/**
	 * Sends a command to turn the controlled robot's turret by the given angle.
	 * 
	 * @param angle The angle to which the robot's turret should turn.
	 */
	public void turnTurret(double angle) {
		done = false;
		int tmp = (int)Math.toDegrees(angle);
		System.out.println(tmp);
		tmp <<= 4;
		tmp += 4;
		tmp <<= 4;
		
		try {
			out.writeInt(tmp);
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
	
	/**
	 * Sends a command to the controlled robot to move by the given distance. (Forward or Backward)
	 * 
	 * @param distance The distance for the robot to travel. Can be positive or negative.
	 */
	public void move(int distance) {
		done = false;
		
		int tmp = distance;
		tmp <<= 4;
		tmp += 2;
		tmp <<= 4;
		
		try {
			out.writeInt(tmp);
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
	
	/**
	 * Sends a command to the controlled robot to fire its main turret.
	 */
	public void fire() {
		done = false;
		int tmp = 3;
		tmp <<= 4;
		
		try {
			out.writeInt(tmp);
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
	
	/**
	 * Tells the controlled robot to clear all commands.
	 */
	public void idle() {
		done = true;
		int tmp = 0;
		
		try {
			out.writeInt(tmp);
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
	
	/**
	 * Sends a command to the controlled robot to transmit the rangersensor's data to the client.
	 */
	public void requestRange() {
		int tmp = 0;
		tmp <<= 4;
		tmp += 1;
		tmp <<= 4;
		tmp += 2;
		
		try {
			out.writeInt(tmp);
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
	
	/**
	 * Requests the controlled Robot to transmit the GroundSensor's data.
	 */
	public void requestGroundData() {
		int tmp = 1;
		tmp <<= 4;
		tmp += 1;
		tmp <<= 4;
		tmp += 2;
		
		try {
			out.writeInt(tmp);
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
	
	/**
	 * Tells the robot to place a mine.
	 */
	public void placeMine() {
		done = false;
		int tmp = 5;
		tmp <<= 4;
		
		try {
			out.writeInt(tmp);
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
	
	protected void ping() {
		try {
			out.writeInt(1);
		} catch (IOException e) {
			e.printStackTrace();
			System.exit(1);
		}
	}
	
	/**
	 * A class for a Thread to ping the server every 2 seconds so the server 
	 * does not consider the client disconnected
	 */
	private class Pinger implements Runnable {
		
		/**
		 * Runs an endless loop to ping the server every 2 seconds.
		 */
		@Override
		public void run() {
			for(;;) {
//				System.out.println(lastPing + ":" + (System.currentTimeMillis() - lastPing));
				if(System.currentTimeMillis() - lastPing >= 2000) {
					ping();
//					System.out.println("PING");
					
					lastPing = System.currentTimeMillis();
				}
				
				int input = -1;
				
				if(in.isReady()) {
					input = in.getInput();
					
//					System.out.println("IN: " + input);
				}
				
				if(input != -1) {
					int type = input & 0xF;
//					System.out.println("TYPE: " + type);
					
					switch(type) {
					case 0:
						int tmp = input >> 4;
//						System.out.println("tmp: " + tmp + "; tmp & 0xF: " + (tmp & 0xF));
		
						switch(tmp & 0xF) {
						case 0:
							distance = tmp >> 16;
							angle = Math.toRadians(((tmp >> 4) & 0x0FFF) % (2 * Math.PI));
//							System.out.println("SENSOR: " + (tmp >> 16) + " : " + (((tmp >> 4) & 0x0FFF) % (2 * Math.PI)));
							break;
						case 1:
							groundSensorData = tmp >> 4;
							break;
						}
						break;
					case 1:
						done = true;
						break;
					case 2:
						stuck = true;
						break;
					case 3:
						stuck = false;
						break;
					}
					
					try {
						Thread.sleep(100);
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
				}
			}
		}
	}
	
	/**
	 * Returns the distance to the object in front of the robot, or -1 if there is nothing.
	 */
	public int getDistance() {
		return distance;
	}
	
	/**
	 * Returns the difference between the angle of the robot towards the object in front of him and the robot plus the robot's turret angle.
	 */
	public double getAngle() {
		return angle;
	}
	
	/**
	 * Returns the int telling what the ground sensor has found in front of the robot.
	 */
	public int getGroundSensorData() {
		return groundSensorData;
	}
	
	/**
	 * Returns if the robot has finished all commands or not.
	 */
	public boolean isDone() {
		return done;
	}

	public boolean isStuck() {
		return stuck;
	}
}
